using System;
using System.IO;
using System.Threading;
using java.util;
using org.jawk.jrt;

namespace org.jawk.ext
{
	/// <summary>Enable stdin processing in Jawk, to be used in conjunction with the -ni parameter.
	/// 	</summary>
	/// <remarks>
	/// Enable stdin processing in Jawk, to be used in conjunction with the -ni parameter.
	/// Since normal input processing is turned off via -ni, this is provided to enable a way
	/// to read input from stdin.
	/// <p>
	/// To use:
	/// <blockquote><pre>
	/// StdinGetline() == 1 { print "--&gt; " $0 }
	/// </pre></blockquote>
	/// <p>
	/// The extension functions are as follows:
	/// <ul>
	/// <hr>
	/// <li><strong><em><font size=+1>StdinHasInput</font></em></strong> -<br />
	/// Returns 1 when StdinGetline() does not block (i.e., when input is available
	/// or upon an EOF), 0 otherwise.<br />
	/// <strong>Parameters:</strong>
	/// <ul>
	/// <li>none
	/// </ul>
	/// <strong>Returns:</strong>
	/// <ul>
	/// <li>1 when StdinGetline() does not block, 0 otherwise.
	/// </ul><p>
	/// <li><strong><em><font size=+1>StdinGetline</font></em></strong> -<br />
	/// Retrieve a line of input from stdin.  The operation
	/// will block until input is available, EOF, or an IO error.<br />
	/// <strong>Parameters:</strong>
	/// <ul>
	/// <li>none
	/// </ul>
	/// <strong>Returns:</strong>
	/// <ul>
	/// <li>1 upon successful read of a line of input from stdin,
	/// 0 upon an EOF, and -1 when an IO error occurs.
	/// </ul><p>
	/// <li><strong><em><font size=+1>StdinBlock</font></em></strong> -<br />
	/// Block until a call to StdinGetline() would not block.
	/// <strong>Parameters:</strong>
	/// <ul>
	/// <li>chained block function - optional
	/// </ul>
	/// <strong>Returns:</strong>
	/// <ul>
	/// <li>"Stdin" if this block object is triggered
	/// </ul><p>
	/// <hr>
	/// </ul>
	/// </remarks>
	public class StdinExtension : org.jawk.ext.AbstractExtension, org.jawk.ext.IJawkExtension
	{
		private readonly java.util.BlockingQueue<object> getline_input = new 
			java.util.BlockingQueue<object>();

		private static readonly object DONE = new object();

		/// <exception cref="System.IO.IOException"></exception>
		public StdinExtension()
		{
			blocker = new _BlockObject_180(this);
			IThread getline_input_thread = new _Thread_72(this, "getline_input_thread");
			// do nothing ... the thread death will signal an issue
			// do nothing ... the thread death will signal an issue
			getline_input_thread.SetDaemon(true);
			getline_input_thread.Start();
		}

		private sealed class _Thread_72 : IThread
		{
            private Thread mThread = null;

			public _Thread_72(StdinExtension _enclosing, string baseArg1)
			{
				this._enclosing = _enclosing;
                mThread = new Thread(new ThreadStart(Run));
                mThread.Name = baseArg1;
			}

            public void Start()
            {
                mThread.Start();
            }

            public void SetDaemon(bool b)
            {
                mThread.IsBackground = b;
            }

			public void Run()
			{
				try
				{
                    //java.io.BufferedReader br = new java.io.BufferedReader(new java.io.InputStreamReader
                    //    (Sharpen.Runtime.@in));

                    
                        string line;
                        while ((line = Console.ReadLine()) != null)
                        {
                            this._enclosing.getline_input.Put(line);
                            lock (this._enclosing.blocker)
                            {
                                Monitor.Pulse(this._enclosing.blocker);
                            }
                        }
                    
					
				}
				catch (System.IO.IOException ioe)
				{
                    Console.WriteLine(ioe.ToString());
				}
				try
				{
					this._enclosing.getline_input.Put(org.jawk.ext.StdinExtension.DONE);
				}
				catch (System.Exception ie)
				{
                    Console.WriteLine(ie.ToString());
					System.Console.Error.WriteLine("Should never be interrupted.");
					System.Environment.Exit(1);
				}
				lock (this._enclosing.blocker)
				{
					Monitor.Pulse(this._enclosing.blocker);
				}
			}

			private readonly StdinExtension _enclosing;

            #region IJoinable Members

            public void Join()
            {
                mThread.Join();
            }

            public void Interrupt()
            {
                mThread.Interrupt();
            }

            #endregion

            #region IBlockable Members

            [Obsolete("Not Implemented", true)]
            public bool WillBlock(BlockObject bo)
            {
                return false;
            }

            #endregion
        }

		public override string GetExtensionName()
		{
			return "Stdin Support";
		}

		public override string[] ExtensionKeywords()
		{
			return new string[] { "StdinHasInput", "StdinGetline", "StdinBlock" };
		}

		// keyboard stuff
		// i.e.  b = StdinHasInput()
		// i.e.  retcode = StdinGetline() # $0 = the input
		// i.e.  StdinBlock(...)
		public override object Invoke(string keyword, object[] args)
		{
				if (keyword.Equals("StdinHasInput"))
				{
					CheckNumArgs(args, 0);
					return Stdinhasinput();
				}
				else
				{
					if (keyword.Equals("StdinGetline"))
					{
						CheckNumArgs(args, 0);
						return Stdingetline();
					}
					else
					{
						if (keyword.Equals("StdinBlock"))
						{
							if (args.Length == 0)
							{
								return Stdinblock();
							}
							else
							{
								if (args.Length == 1)
								{
									return Stdinblock((org.jawk.jrt.BlockObject)args[0]);
								}
								else
								{
									throw new System.ArgumentException("StdinBlock accepts 0 or 1 args.");
								}
							}
						}
						else
						{
							throw new org.jawk.NotImplementedError(keyword);
						}
					}
				}
			
		}

		private bool is_eof = false;

		private int Stdinhasinput()
		{
			if (is_eof)
			{
				// upon eof, always "don't block" !
				return 1;
			}
			else
			{
				if (getline_input.Count == 0)
				{
					// nothing in the queue
					return 0;
				}
				else
				{
					if (getline_input.Count == 1 && getline_input.Peek() == DONE)
					{
						// DONE indicator in the queue
						return 0;
					}
					else
					{
						// otherwise, something to read
						return 1;
					}
				}
			}
		}

		/// <returns>
		/// 1 upon successful read,
		/// 0 upon EOF, and -1 if an IO error occurs
		/// </returns>
		private object Stdingetline()
		{
			try
			{
				if (is_eof)
				{
					return 0;
				}
				object line_obj = getline_input.Take();
				if (line_obj == DONE)
				{
					is_eof = true;
					return 0;
				}
				jrt.input_line = (string)line_obj;
				jrt.JrtParseFields();
				return 1;
			}
			catch (System.Exception ie)
			{
				Console.Error.WriteLine(ie);
				return -1;
			}
		}

		private sealed class _BlockObject_180 : org.jawk.jrt.BlockObject
		{
			public _BlockObject_180(StdinExtension _enclosing)
			{
				this._enclosing = _enclosing;
			}

			public override string GetNotifierTag()
			{
				return "Stdin";
			}

			/// <exception cref="System.Exception"></exception>
			public sealed override void Block()
			{
				lock (this._enclosing.blocker)
				{
					if (this._enclosing.Stdinhasinput() == 0)
					{
						Monitor.Wait(this._enclosing.blocker);
					}
				}
			}

			private readonly StdinExtension _enclosing;
		}

		private org.jawk.jrt.BlockObject blocker;

		private org.jawk.jrt.BlockObject Stdinblock()
		{
			blocker.ClearNextBlockObject();
			return blocker;
		}

		private org.jawk.jrt.BlockObject Stdinblock(org.jawk.jrt.BlockObject bo)
		{
			//assert bo != null;
			blocker.SetNextBlockObject(bo);
			return blocker;
		}
	}
}
